using Sandia.MVCFramework.Common;
using Sandia.MVCFramework.Data;
using Sandia.MVCFramework.DataAccessor;
using Sandia.MVCFramework.DataAnalyzer;
using Sandia.MVCFramework.DataStore;
using Sandia.MVCFramework.Request;
using Sandia.MVCFramework.ViewManager;
using System;
using System.Collections.Generic;
using System.Text;
using Sandia.MVCFramework.ExportTools;

namespace Sandia.MVCFramework.Plugin
{
    /// <summary>
    /// A standard abstract implementation of IPlugin.
    /// </summary>
    public abstract class AbstractStandardPlugin : IPlugin
    {
        /// <summary>
        /// The request router for this plugin.
        /// </summary>
        protected IRequestRouter requestRouter;

        /// <summary>
        /// The publisher for this plugin.
        /// </summary>
        protected IPublisher publisher;

        /// <summary>
        /// The internal list of desired requests.
        /// </summary>
        private List<Type> desiredRequests = new List<Type>();

        /// <summary>
        /// Whether or not this item has already been disposed
        /// </summary>
        private bool disposed = false;

        /// <summary>
        /// Constructor for AbstractStandardPlugin.
        /// </summary>
        protected AbstractStandardPlugin()
        {
            this.desiredRequests.Add(typeof(AddNewViewManagerRequest));
        }

        #region IPlugin Members

        /// <summary>
        /// Initialize the plugin
        /// </summary>
        /// <param name="requestRouter">The request router to be used by the plugin</param>
        /// <param name="publisher">The publisher to be used by the plugin</param>
        public void Init(IRequestRouter requestRouter, IPublisher publisher)
        {
            this.requestRouter = requestRouter;
            this.publisher = publisher;
            this.init(requestRouter, publisher);
        }

        /// <summary>
        /// Initialize the plugin
        /// </summary>
        /// <param name="requestRouter">The request router to be used by the plugin</param>
        /// <param name="publisher">The publisher to be used by the plugin</param>
        public abstract void init(IRequestRouter requestRouter, IPublisher publisher);

        /// <summary>
        /// The unique ID for this plugin.
        /// </summary>
        public abstract string ID
        {
            get;
        }

        /// <summary>
        /// The display name for this plugin.
        /// </summary>
        public abstract string DisplayName
        {
            get;
        }

        /// <summary>
        /// The plugin loader version this plugin is writen to
        /// </summary>
        public abstract long LoaderVersionToUse
        {
            get;
        }

        /// <summary>
        /// A list of plugin Ids that this plugin is dependant on.
        /// </summary>
        public abstract List<string> Dependencies
        {
            get;
        }

        /// <summary>
        /// DataAccessors provided by the plugin
        /// </summary>
        public abstract List<IDataAccessor> DataAccessors
        {
            get;
        }

        /// <summary>
        /// DataAnalyzers provided by the plugin
        /// </summary>
        public abstract List<IDataAnalyzer> DataAnalyzers
        {
            get;
        }

        /// <summary>
        /// DataStores provided by the plugin
        /// </summary>
        public abstract List<IDataStore> DataStores
        {
            get;
        }

        /// <summary>
        /// All of the request routes discovered by this plugin
        /// </summary>
        public Dictionary<Type, List<IRequestProcessor>> RequestRoutings
        {
            // The key to this collection is that it is a list of 
            // desired requests for all of the objects in this plugin.
            // The collection then can be used to call all of the 
            // ProcessRequest methods when a request of a certain type
            // is produced.

            get
            {
                // A temporary collection of request processors.
                //
                Dictionary<Type, List<IRequestProcessor>> requestRoutings =
                    new Dictionary<Type, List<IRequestProcessor>>();

                // Loop through the DataAccessors known by this plugin
                // 
                foreach (IDataAccessor dataAccessor in this.DataAccessors)
                {
                    this.addRequestRoutings(dataAccessor, requestRoutings);
                }

                // Loop through the DataAnalyzers
                //
                foreach (IDataAnalyzer dataAnalyzer in this.DataAnalyzers)
                {
                    this.addRequestRoutings(dataAnalyzer, requestRoutings);
                }

                // Loop through the DataStores
                //
                foreach (IDataStore dataStore in this.DataStores)
                {
                    this.addRequestRoutings(dataStore, requestRoutings);
                }

                foreach (IImporter importer in this.Importers)
                {
                    this.addRequestRoutings(importer, requestRoutings);
                }

                // 'this' plugin itself can have 'desired requests' too
                //
                this.addRequestRoutings(this, requestRoutings);

                // now that the collection is complete, return it
                //
                return requestRoutings;
            }
        }

        /// <summary>
        /// Adds the desired request types for the given request processor to
        /// the list of request routings.
        /// </summary>
        /// <param name="requestProcessor">The request processor to add.</param>
        /// <param name="requestRoutings">The list of request routings.</param>
        private void addRequestRoutings(
            IRequestProcessor requestProcessor,
            Dictionary<Type, List<IRequestProcessor>> requestRoutings)
        {
            foreach (Type requestType in requestProcessor.DesiredRequests)
            {
                // If an entry for this request type does not already
                // exist in the routing collection, creaate it.  The
                // entries themselves are lists because there can be
                // more than one request processor for each type of 
                // request.
                //
                if (!requestRoutings.ContainsKey(requestType))
                {
                    requestRoutings[requestType] = new List<IRequestProcessor>();
                }

                // Since we've guaranteed that the entry (list) for this type
                // of request exists, add it.
                //
                requestRoutings[requestType].Add(requestProcessor);
            }
        }

        /// <summary>
        /// Subscriptions provided by the plugin.
        /// </summary>
        public Dictionary<Type, List<ISubscriber>> Subscriptions
        {
            get
            {
                Dictionary<Type, List<ISubscriber>> subscriptions = 
                    new Dictionary<Type, List<ISubscriber>>();
                foreach (IDataAnalyzer dataAnalyzer in this.DataAnalyzers)
                {
                    this.addSubscriptions(dataAnalyzer, subscriptions);
                }
                foreach (IDataStore dataStore in this.DataStores)
                {
                    this.addSubscriptions(dataStore, subscriptions);
                }
                foreach (IViewManager viewManager in this.ViewManagers)
                {
                    this.addSubscriptions(viewManager, subscriptions);
                }
                foreach (IExporter exporter in this.Exporters)
                {
                    this.addSubscriptions(exporter, subscriptions);
                }
                return subscriptions;
            }
        }

        /// <summary>
        /// Adds the desired subscription types for the given subscriber to the
        /// list of subscriptions.
        /// </summary>
        /// <param name="subscriber">The subscriber to add.</param>
        /// <param name="subscriptions">The list of subscriptions.</param>
        private void addSubscriptions(
            ISubscriber subscriber,
            Dictionary<Type, List<ISubscriber>> subscriptions)
        {
            foreach (Type subscriptionType in subscriber.DesiredSubscriptions)
            {
                if (!subscriptions.ContainsKey(subscriptionType))
                {
                    subscriptions[subscriptionType] = new List<ISubscriber>();
                }
                subscriptions[subscriptionType].Add(subscriber);
            }
        }

        /// <summary>
        /// ViewManagers provided by the plugin
        /// </summary>
        public abstract List<IViewManager> ViewManagers
        {
            get;
        }

        /// <summary>
        /// Exporters provided by the plugin.
        /// </summary>
        public abstract List<IExporter> Exporters
        {
            get;
        }

        /// <summary>
        /// Importers provided by the plugin.
        /// </summary>
        public abstract List<IImporter> Importers
        {
            get;
        }

        #endregion

        #region IStartable Members

        /// <summary>
        /// Called when MVC Framework initialization has completed and
        /// transactions can begin.
        /// </summary>
        /// <remarks>
        /// IRequests and IData should not be sent before this method is called, 
        /// as this can cause race conditions while an application is 
        /// initializing.
        /// </remarks>
        public abstract void Start();

        #endregion

        #region IRequestProcessor Members

        /// <summary>
        /// The IRequest types that this RequestProcessor would like to process.
        /// </summary>
        public IList<Type> DesiredRequests
        {
            get { return this.desiredRequests; }
        }

        /// <summary>
        /// Processes an incoming request
        /// </summary>
        /// <param name="request">The IRequest to process</param>
        public void ProcessRequest(
            IRequest request)
        {
            AddNewViewManagerRequest anvmr = request as AddNewViewManagerRequest;
            if (anvmr != null)
            {
                try
                {
                    // If this is an AddNewViewManagerRequest, we repackage it as
                    // an AddViewManagerRequest with the plugin's ID and send it
                    // on.
                    AddViewManagerRequest avmr = new AddViewManagerRequest();
                    avmr[ViewManagerFactoryRequestParameter.NAME] = 
                        anvmr[ViewManagerFactoryRequestParameter.NAME];
                    avmr[PluginIdRequestParameter.NAME] = 
                        new PluginIdRequestParameter(this.ID);
                    // send the request
                    this.requestRouter.RouteRequest(avmr);
                }
                catch(Exception e)
                {
                    this.sendErrorToLog("Error in AbstractStandardPlugin.ProcessRequest: " + e.Message);
                }
            }
            else
            {
                // Pass it to inheriting classes to process.
                this.processRequest(request);
            }
        }

        /// <summary>
        /// Method to be used by inheriting classes to process their own requests.
        /// This method is called if the abstract class is unable to process the
        /// request.
        /// </summary>
        /// <param name="request">
        /// The IRequest to process.
        /// </param>
        protected abstract void processRequest(
            IRequest request);

        /// <summary>
        /// Whether or not this item has been disposed
        /// </summary>
        public bool IsDisposed
        {
            get { return this.disposed; }
        }

        /// <summary>
        /// Called while disposing.
        /// </summary>
        public event EventHandler Disposed;

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, 
        /// releasing, or resetting unmanaged resources.
        /// </summary>
        public virtual void Dispose()
        {
            disposed = true;
            if (Disposed != null) Disposed(this, new EventArgs());
        }

        #endregion

        #region IDataPacketProducer Members

        /// <summary>
        /// Initialize the data packet producer by telling
        /// it who publishes the data packets produced
        /// </summary>
        /// <param name="publisher">The publisher for data
        /// packets produced</param>
        public void InitDataPacketProducer(
            IPublisher publisher)
        {
            this.publisher = publisher;
        }

        #endregion

        /// <summary>
        /// Sends an error message to the log.
        /// </summary>
        /// <param name="message">The message to send.</param>
        private void sendErrorToLog(
            String message)
        {
            // Generate a log entry
            LoggingTools.SendToLog(
                message,
                LoggingCategories.ERROR,
                100);
        }
    }
}
